<?php
/**
 * 文件上传
 * 
 * $Id: fastupload.php 123 2010-05-30 19:55:05Z Administrator $
 * $Revision: 123 $
 * $Author: Administrator $
 * $Date: 2010-05-31 03:55:05 +0800 (星期一, 31 五月 2010) $
 * 初步实现服务器文件智能上传和管理。支持多个文件进行上传。
 * 
 * @author glzaboy<glzaboy@163.com>
 * @package fastlib
 * @subpackage classes
 * @copyright 2005-2010 (c) glzaboy
 */
if (! defined ( '__FASTLIB__' )) {
	if (__DEBUG__) {
		die ( sprintf ( 'Forbidden 403 at file %s on line %d', __FILE__, __LINE__ ) );
	} else {
		die ( 'Forbidden 403' );
	}
}
/**
 * 文件上传
 * 
 * 初步实现服务器文件智能上传和管理。支持多个文件进行上传。
 *
 * @author glzaboy<glzaboy@163.com>
 */
class fastupload {
	/**
	 * 是否运行出错
	 *
	 * @var bool
	 */
	private $halt = false;
	/**
	 * 日志记录对象
	 *
	 * @var CLASSOBJECT
	 */
	private $logObj = null;
	/**
	 * 相对于调用脚本时外界访问附件的ＵＲＬ
	 *
	 * @var String
	 */
	private $urlbase = null;
	/**
	 * 操作目录
	 *
	 * @var String
	 */
	private $ddir = null;
	/**
	 * 工作目录
	 *
	 * @var String
	 */
	private $workdir = null;
	/**
	 * 允许上传的扩展名
	 *
	 * @var array
	 */
	private $ext = array ();
	/**
	 * 初始化上传类
	 *
	 * @param string $basedir 基础目录
	 * @param String $ext 允许的扩展名
	 * @param String $subdir 配置下的子目录
	 * @param String $urlbase 网络访问路径
	 * @param string $hash Hash字串0-9
	 * @return fastupload
	 */
	public function __construct($basedir = '.', $ext = 'txt', $subdir = null, $urlbase = './', $hash = '') {
		if (! is_dir ( $basedir )) {
			$this->putlog ( 'fastupload base dir ' . $basedir . ' not exists!', __FILE__, __LINE__ );
			$this->halt = true;
			return false;
		}
		$this->workdir = $basedir;
		$this->urlbase = $urlbase;
		if (is_string ( $subdir )) {
			$this->ddir = $subdir;
		}
		if (is_string ( $hash ) && $hash != '') {
			if ($this->ddir) {
				$this->ddir .= '/' . $this->hash ( $hash );
			} else {
				$this->ddir = $this->hash ( $hash );
			}
		}
		$this->ext = explode ( ',', $ext );
		$this->getDir ();
		return true;
	}
	/**
	 * 根据数字返回目录hash
	 *
	 * @param int $hash hash数字
	 * @return String 目录hash
	 */
	private function hash($hash = '') {
		$hashtable = array ("fj", "js", "bj", "sh", "zj", "cq", "zxf", "glz", "hfjj", "hlx" );
		$returnValue = '';
		for($offset = 0; isset ( $hash {$offset} ); ++ $offset) {
			$returnValue .= $hashtable [$hash [$offset]];
		}
		return ( string ) $returnValue;
	}
	/**
	 * 根据字符串创建目录
	 *
	 * @param String	$basedir 基准目录
	 * @param String	$dir 创建的目录，支持多级
	 * @param Int	$arrt 创建目录权限
	 */
	private function createDir($basedir, $dir, $arrt) {
		$tmpdir = explode ( '/', $dir );
		$cDir = $basedir;
		while ( null !== ($pdir = array_shift ( $tmpdir )) ) {
			touchdir ( $cDir .= '/' . $pdir, $arrt );
		}
	}
	/**
	 * 返回网站相对目录
	 *
	 * @param bool $absolute 是否采用绝对路径，绝对路径是在相对基础上加上urlbase
	 * @return String 返回的路径
	 */
	public function getUrlDir($absolute = false) {
		if ($this->halt) {
			return null;
		} else {
			if (! is_dir ( $this->workdir . '/' . $this->ddir )) {
				$this->createDir ( $this->workdir, $this->ddir, 0777 );
			}
			if ($absolute) {
				return $this->ddir;
			} else {
				return $this->urlbase . '/' . $this->ddir;
			}
		}
	}
	/**
	 * 返回服务器上上传路径
	 *
	 * @return String
	 */
	public function getDir() {
		if ($this->halt) {
			return null;
		} else {
			if (! is_dir ( $this->workdir . '/' . $this->ddir )) {
				$this->createDir ( $this->workdir, $this->ddir, 0777 );
			}
			return $this->workdir . '/' . $this->ddir;
		}
	}
	/**
	 * 取得附件目录下文件列表
	 *
	 * 文件列表元素中包含，
	 * 文件名(name)<br />
	 * 大小(size)<br />
	 * 上次访问时间(atime)<br />
	 * 文件的 inode修改时间相当于创建时间(ctime)<br />
	 * 修改时间(mtime)<br />相对当前路径(url)<br />
	 * 扩展名(ext)<br />
	 * 
	 * @return Array 文件列表属性
	 */
	public function fileList() {
		if ($this->halt) {
			return null;
		} else {
			$fileList = array ();
			$dir = dir ( $this->getDir () );
			while ( false !== ($tmpfile = $dir->read ()) ) {
				if (is_dir ( $this->getDir () . '/' . $tmpfile ) or in_array ( $tmpfile, array ('index.php', 'index.html', '.', '..' ) )) {
					continue;
				}
				$tmpItem = array ();
				$tmpItem ['name'] = $tmpfile;
				$tmpItem ['size'] = filesize ( $this->getDir () . '/' . $tmpfile );
				$tmpItem ['atime'] = fileatime ( $this->getDir () . '/' . $tmpfile );
				$tmpItem ['mtime'] = filemtime ( $this->getDir () . '/' . $tmpfile );
				$tmpItem ['ctime'] = filectime ( $this->getDir () . '/' . $tmpfile );
				$tmpItem ['ext'] = array_pop ( explode ( '.', $tmpfile ) );
				$tmpItem ['url'] = $this->getUrlDir () . "/" . $tmpItem ['name'];
				array_push ( $fileList, $tmpItem );
			}
			return $fileList;
		}
	}
	/**
	 * 查找当前附件目录下文件是否存在
	 *
	 * @param String $file 文件名
	 * @return bool 是否存在
	 */
	public function fileCheck($file) {
		if ($this->halt) {
			return null;
		}
		if (in_array ( $file, array ('.', '..' ) )) {
			return false;
		}
		if (strpos ( $file, '/' ) or strpos ( $file, "\\" )) {
			return false;
		}
		if (file_exists ( $this->getDir () . '/' . $file )) {
			return true;
		}
		return false;
	}
	/**
	 * 删除当前工作目录下所有文件
	 *
	 * @param bool $error 当为true时出错时会再执行删除一次，如果出错就直接返回false
	 * @return bool 是否成功删除所有文件
	 */
	public function deleteAllFile($error = false) {
		$tmpd = dir ( $this->getDIR () );
		while ( false !== ($filename = $tmpd->read ()) ) {
			if (is_dir ( $filename ) or $filename == "." or $filename == "..") {
				continue;
			}
			if (! @unlink ( $this->getDIR () . "/" . $filename ) and $error) {
				return false;
			}
		}
		$tmpd->close ();
		return true;
	}
	/**
	 * 删除当前工作目录
	 *
	 * @return bool 删除成功与否
	 */
	public function deleteDir() {
		if ($this->deleteAllFile ( false ) && rmdir ( $this->getDIR () )) {
			return true;
		} else {
			return false;
		}
	}
	/**
	 * 删除工作目录下指定的文件
	 *
	 * @param String $filename 文件名
	 * @return bool 删除是否成功
	 */
	public function deleteFile($filename) {
		if ($this->halt) {
			return null;
		}
		if (! $this->fileCheck ( $filename )) {
			return false;
		}
		if (is_file ( $this->getDIR () . "/" . $filename ) && unlink ( $this->getDIR () . "/" . $filename )) {
			return true;
		} else {
			return false;
		}
	}
	/**
	 * 保存上传来的文件
	 *
	 * @return Array 保存的结果
	 */
	public function save() {
		if (count ( $_FILES ) >= 1) {
			$returnArr = array ();
			foreach ( $_FILES as $k => $v ) {
				if (is_array ( $v ['name'] )) {
					foreach ( $v ['error'] as $mk => $mv ) {
						if (in_array ( $fext = array_pop ( explode ( '.', $v ['name'] [$mk] ) ), $this->ext )) {
							if ($v ['error'] [$mk] != UPLOAD_ERR_OK) {
								$this->putlog ( 'an error make ' . $v ['name'] [$mk] . ' upload fail!', __FILE__, __LINE__ );
								$returnArr [$k] ['ERR'] [$mk] = $v ['error'] [$mk];
								$returnArr [$k] ['STATUS'] [$mk] = "FAIL";
								$returnArr [$k] ['name'] [$mk] = $v ['name'] [$mk];
								continue;
							}
							$fileName = sha1_file ( $v ['tmp_name'] [$mk] ) . md5_file ( $v ['tmp_name'] [$mk] ) . '.' . $fext;
							if (move_uploaded_file ( $v ['tmp_name'] [$mk], $this->getDir () . "/" . $fileName )) {
								$this->putlog ( 'File ' . $v ['name'] [$mk] . ' upload to ' . $this->getDir () . "/" . $fileName . 'sucess!', __FILE__, __LINE__ );
								$returnArr [$k] ['name'] [$mk] = $fileName;
								$returnArr [$k] ['STATUS'] [$mk] = "SUCCESS";
								$returnArr [$k] ['url'] [$mk] = $this->getUrlDir ( true ) . '/' . $fileName;
							} else {
								$this->putlog ( 'File ' . $v ['name'] [$mk] . ' upload to ' . $this->getDir () . "/" . $fileName . 'fail!', __FILE__, __LINE__ );
								$returnArr [$k] ['ERR'] [$mk] = "move file fail.";
								$returnArr [$k] ['STATUS'] [$mk] = "FAIL";
								$returnArr [$k] ['name'] [$mk] = $v ['name'] [$mk];
							}
						} else {
							$this->putlog ( 'File ' . $v ['name'] [$mk] . ' disallow upload!', __FILE__, __LINE__ );
							$returnArr [$k] ['ERR'] [$mk] = "disallow upload";
							$returnArr [$k] ['STATUS'] [$mk] = "DISABLE";
							$returnArr [$k] ['name'] [$mk] = $v ['name'] [$mk];
						}
						$mk = $mv;
						unset ( $mv, $mk );
					}
				} else {
					if (in_array ( $fext = array_pop ( explode ( '.', $v ['name'] ) ), $this->ext )) {
						if ($v ['error'] != UPLOAD_ERR_OK) {
							$this->putlog ( 'an error make ' . $v ['name'] . ' upload fail!', __FILE__, __LINE__ );
							$returnArr [$k] ['ERR'] = $v ['error'];
							$returnArr [$k] ['STATUS'] = "FAIL";
							$returnArr [$k] ['name'] = $v ['name'];
							continue;
						}
						$fileName = sha1_file ( $v ['tmp_name'] ) . md5_file ( $v ['tmp_name'] ) . '.' . $fext;
						if (move_uploaded_file ( $v ['tmp_name'], $this->getDir () . "/" . $fileName )) {
							$this->putlog ( 'File ' . $v ['name'] . ' upload to ' . $this->getDir () . "/" . $fileName . 'sucess!', __FILE__, __LINE__ );
							$returnArr [$k] ['name'] = $fileName;
							$returnArr [$k] ['url'] = $this->getUrlDir ( true ) . '/' . $fileName;
							$returnArr [$k] ['STATUS'] = "SUCCESS";
						} else {
							$this->putlog ( 'File ' . $v ['name'] . ' upload to ' . $this->getDir () . "/" . $fileName . 'fail!', __FILE__, __LINE__ );
							$returnArr [$k] ['ERR'] = "move file fail.";
							$returnArr [$k] ['STATUS'] = "FAIL";
							$returnArr [$k] ['name'] = $v ['name'];
						}
					} else {
						$this->putlog ( 'File ' . $v ['name'] . ' disallow upload!', __FILE__, __LINE__ );
						$returnArr [$k] ['ERR'] = "disallow upload";
						$returnArr [$k] ['STATUS'] = "DISABLE";
						$returnArr [$k] ['name'] = $v ['name'];
					}
				}
			}
			return $returnArr;
		} else {
			return false;
		}
	}
	/**
	 * 记录日志
	 *
	 * @param String $message 日志内容
	 * @param String $file 调用文件名
	 * @param Int $line 调用文件行数
	 * @return bool 是否成功写入日志
	 */
	private function putlog($message, $file, $line) {
		if (! $this->logObj) {
			global $flibcfg;
			$this->logObj = new log ( $flibcfg ['logTimeFormat'], __CLASS__ . '.' . $flibcfg ['logFileName'] );
		}
		return $this->logObj->putlog ( $message, $file, $line );
	}
	/**
	 * 返回本模块信息
	 *
	 * @return string
	 */
	public function printInfo() {
		$version = '$Revision: 123 $';
		$moudle = __CLASS__;
		$return = '';
		$return .= startTable ();
		$return .= printRow ( array (array ('class' => 'h', 'content' => '<center>' . $moudle . '</center>', 'colspan' => '2' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'author' ), array ('class' => 'v', 'content' => 'glzaboy' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'version' ), array ('class' => 'v', 'content' => $version ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'Suport' ), array ('class' => 'v', 'content' => 'File upload, list, download, delete' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'Multi-file upload' ), array ('class' => 'v', 'content' => 'YES' ) ) );
		$return .= endTable ();
		return $return;
	}
}